﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Hopfield
{
    internal class Program
    {
        private static void Main()
        {
            try
            {
                HopfieldMain(Environment.CurrentDirectory);
            }
            catch (Exception ex)
            {
                Console.WriteLine("+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-");
                Console.WriteLine("Uncought exception: {0}", ex);
                Console.WriteLine("Enter!");
                Console.ReadKey();
            }
        }

        private static void HopfieldMain(string workingDirectory)
        {
            //read known patterns (=all images not having input or output in there names)
            List<int[][]> patterns = PatternHelper.ReadPatterns(workingDirectory + @"/patterns");

            if (patterns.Count == 0)
            {
                Console.WriteLine("No pattern-images found in '{0}'", workingDirectory);
                Console.WriteLine("Enter!");
                Console.ReadKey();
                return;
            }

            //read input-file
            int[][] input = PatternHelper.ReadInputPattern(workingDirectory + @"/input");

            if (input == null)
            {
                Console.WriteLine("No input was found!");
                Console.WriteLine("Enter!");
                Console.ReadKey();
                return;
            }

            //do patterns exist with different dimensions?
            IEnumerable<int[][]> wrongPatterns = patterns.Where(p => p.Count() != patterns[0].Count() || p[0].Count() != patterns[0][0].Count());
            if (wrongPatterns.Any())
            {
                Console.WriteLine("Not all images have the same dimensions!");
                Console.WriteLine("Enter!");
                Console.ReadKey();
                return;
            }

            //there is a neuron for each pixel
            int dx = patterns[0].Count();
            int dy = patterns[0][0].Count();
            int neuroncount = dx*dy;

            double[][][][] weights = GetWeights(patterns, dx, dy, neuroncount);

            string outputDirectory = workingDirectory + @"/output";
            Directory.CreateDirectory(outputDirectory);
            foreach (var file in Directory.EnumerateFiles(outputDirectory, "output-step-*.png"))
            {
                File.Delete(file);
            }
            bool changed;
            int step = 0;
            do
            {
                PatternHelper.WritePatternToImageFile(outputDirectory, "output-step-" + step, input);
                Console.WriteLine("Doing round {0}", ++step);
                changed = false;
                int[][] inputNeu = PatternHelper.GetArray<int>(dx, dy);
                for (int ix = 0; ix < dx; ix++)
                {
                    for (int iy = 0; iy < dy; iy++)
                    {
                        int oldval = input[ix][iy];

                        double movingSum = 0.0;
                        for (int jx = 0; jx < dx; jx++)
                        {
                            for (int jy = 0; jy < dy; jy++)
                            {
                                movingSum += weights[ix][iy][jx][jy]*input[jx][jy];
                            }
                        }

                        int newval = movingSum >= 0.0 ? 1 : -1;
                        inputNeu[ix][iy] = newval;

                        changed = changed || oldval != newval;
                    }
                }
                input = inputNeu;
            } while (changed);


            Console.WriteLine("No more changes after " + step + " Rounds!");
            Console.WriteLine("<Enter> !");
            Console.ReadKey();
        }

        private static double[][][][] GetWeights(List<int[][]> patterns, int dx, int dy, int neuroncount)
        {
            Console.WriteLine("Calculating weights...");
            Stopwatch sw = Stopwatch.StartNew();
            //hopfield-nets are fully connected => there is a weight between each neuron
            double[][][][] weights = PatternHelper.GetArray<double[][]>(dx, dy);

            double inverseNeuronCount = 1.0 / neuroncount;
            Parallel.For(0, dx, ix =>
                                    {
                                        for (int iy = 0; iy < dy; iy++)
                                        {
                                            weights[ix][iy] = PatternHelper.GetArray<double>(dx, dy);
                                            for (int jx = 0; jx < dx; jx++)
                                            {
                                                for (int jy = 0; jy < dy; jy++)
                                                {
                                                    if (ix == jx && iy == jy)
                                                    {
                                                        weights[ix][iy][jx][jy] = 0;
                                                    }
                                                    else
                                                    {
                                                        //weight from i to j
                                                        double weight = inverseNeuronCount*patterns.Select(p => p[ix][iy]*p[jx][jy]).Sum();
                                                        weights[ix][iy][jx][jy] = weight;
                                                    }
                                                }
                                            }
                                        }
                                    });
            Console.WriteLine("Calculating weights...finished. ({0})", sw.Elapsed);
            return weights;
        }
    }
}